from flask import Flask, render_template, request, redirect, url_for, session, flash
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from flask_principal import Principal, Permission, RoleNeed, Identity, identity_changed, AnonymousIdentity, \
    identity_loaded, UserNeed, ActionNeed
from validators import email as validate_email
from loguru import logger
from flask_talisman import Talisman
from typing import Literal
import os
import db_document_manager
import db_public_document_manager
import db_login_manager
import db_user_manager
import db_permission_manager
import db_backup
import forms



# 创建Flask应用实例
app = Flask(__name__)
# 设置用于加密会话数据的密钥
app.secret_key = '123456'

# 配置内容安全策略
csp = {
    'default-src': ['\'self\'', 'https://trusted.com'],
    'script-src': ['\'self\'', 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js']
}

# 配置Talisman，启用强制HTTPS访问等相关安全配置
talisman = Talisman(
    app,
    content_security_policy=csp,
    force_https=True,
    strict_transport_security={
        'max_age': 31536000  # 设置HSTS有效期为1年，单位为秒
    },
    frame_options='DENY'
)


# 配置日志
# 配置日志文件存储路径及相关设置
log_path = os.path.join(os.getcwd(), "logs")
if not os.path.exists(log_path):
    os.makedirs(log_path)
log_file = os.path.join(log_path, "app.log")
logger.add(log_file, rotation="04:00", format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}")

# 初始化 Flask-Principal
principals = Principal(app)

# 定义一些权限
# 角色名（Role Names）：
# 1. 系统管理员 - `sysAdmin` has 用户管理  - 用户名：1
# 2. 安全保密管理员 - `secAdmin`has 权限管理 - 2
# 3. 安全审计员 - `secAuditor` has 日志管理 - 3
# 4. 审核员 - `auditor` has 公文审核 公文签收 - 4
# 5. 操作员 - `operator` has 公文一般操作 - 5
# 权限（Permissions）：
# 1. 用户管理 - `userMgmt`
# 2. 权限管理 - `permMgmt`
# 3. 日志管理 - `logMgmt`
# 4. 公文审核 - `docReview`
# 5. 公文签收 - `docSign`
# 6. 公文一般操作 - `docOps`
doc_ops_permission = Permission(ActionNeed('docOps'))
doc_review_permission = Permission(ActionNeed('docReview'))
doc_sign_permission = Permission(ActionNeed('docSign'))
user_mgmt_permission = Permission(ActionNeed('userMgmt'))
perm_mgmt_permission = Permission(ActionNeed('permMgmt'))
log_mgmt_permission = Permission(ActionNeed('logMgmt'))

# 初始化Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'index_login'  # 未登录时重定向到登录页面


# 用户类
class User(UserMixin):
    def __init__(self, user_id, username,user_unit):
        self.id = user_id
        self.username = username
        self.user_unit = user_unit

# 用户加载回调函数
@login_manager.user_loader
def load_user(user_id):
    user_data = db_login_manager.get_user_by_id(user_id)

    if user_data:
        # 获取角色和权限信息
        roles = db_permission_manager.get_user_roles(user_id)
        permissions = db_permission_manager.get_user_permissions(user_id)
        # 发出信号，通知更新用户身份。
        identity = Identity(user_id)
        identity_changed.send(app, identity=identity)
        # 将角色添加到身份
        for role in roles:
            identity.provides.add(RoleNeed(role))
        # 将权限添加到身份
        for permission in permissions:
            identity.provides.add(ActionNeed(permission))
        return User(user_data['UserID'], user_data['Username'],user_data['Unit'])
    return None

# 定义根路由（'/'）对应的视图函数，用于渲染登录页面（login.html）
@app.route('/')
def index_login():
    form = forms.LoginForm()
    return render_template('login.html', form=form)

# 定义登出路由（'/logout'）对应的视图函数，从会话中移除用户信息，然后重定向到登录页面
@app.route('/logout')
@login_required
def index_logout():
    logger.info(f'current_user:{current_user.id,current_user.username,current_user.user_unit} 登出系统')
    logout_user()
    identity_changed.send(app, identity=AnonymousIdentity())

    return redirect(url_for('index_login'))

# 定义'/index'路由对应的视图函数，判断会话中是否存在用户，
# 如果存在则渲染主页面（index.html），否则重定向到登录页面
@app.route('/index')
@login_required
def index():
    return render_template('index.html')

# 处理登录表单提交（POST请求）的路由'/login'对应的视图函数
@app.route('/login', methods=['POST'])
def login():
    form = forms.LoginForm()
    if form.validate_on_submit():
        name = form.username.data
        pwd = form.password.data
        email = form.email.data

        if not email or not pwd or not name:
            flash('用户们、邮箱或密码不能为空！')
            return redirect(url_for('index_login'))
        verify_result,user_data = db_login_manager.verify_user_login(email,name,pwd)
        if verify_result:
            user = User(user_data['UserID'],user_data['Username'],user_data['Unit'])
            login_user(user)

            user_id = user_data['UserID']
            # 获取用户角色和权限
            roles = db_permission_manager.get_user_roles(user_id)
            permissions = db_permission_manager.get_user_permissions(user_id)

            # 设置身份
            identity = Identity(user_id)

            identity_changed.send(app, identity=identity)
            # 将角色添加到身份
            for role in roles:
                identity.provides.add(RoleNeed(role))
            # 将权限添加到身份
            for permission in permissions:
                identity.provides.add(ActionNeed(permission))

            logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 成功登录！')
            return redirect(url_for('index'))
        #TODO:应该有一个计数器来惩罚多次错误登录。
        logger.warning(f"未登录用户 {name} 尝试登录，密码错误或用户不存在")
        flash('登录失败，请检查您的用户名、邮箱和密码。')
        return redirect(url_for('index_login'))
    else:
        flash('请检查表单输入是否合法')
        return redirect(url_for('index_login'))


@app.route('/user/list')
@login_required
@user_mgmt_permission.require(http_exception=403)
def user_list():
    users_list = db_user_manager.get_user_list()
    return render_template('user_list.html',users_list=users_list)

@app.route('/user/delete/<int:id>', methods=['POST'])
@login_required
@user_mgmt_permission.require(http_exception=403)
def user_delete(id):
    #TODO: 判断能否删除某个用户
    if id == current_user.id:
        logger.warning(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 尝试删除自己用户信息！')
        flash("不可以删除自己！！！")
        return redirect(url_for('user_list'))
    try:
        result = db_user_manager.delete_user(id)
        if result:
            logger.warning(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 删除了用户(ID:{id})！')
            flash("删除的用户ID是： " + str(id))
        else:
            logger.warning(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 尝试删除了用户(ID:{id})失败！')
            flash("删除失败！请确保该用户已无任何角色与权限！")
    except Exception as e:
        flash("删除失败！错误：")
        logger.error(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 删除用户(ID:{id})失败！')
    return redirect(url_for('user_list'))

@app.route('/user/create', methods=['POST'])
@login_required
@user_mgmt_permission.require(http_exception=403)
def user_create():
    username = request.form.get('newUsername')  # 从表单中获取用户名
    email = request.form.get('newEmail')  # 从表单中获取邮箱
    print(username, email)
    if not username or not email or not validate_email(email):
        flash('用户名和邮箱不能为空，且邮箱格式必须正确', 'danger')
        return redirect(url_for('user_list'))
    init_pwd = "123456"
    init_unit = current_user.user_unit
    try:
        result = db_user_manager.create_user(username, init_pwd, email, init_unit)
        if result:
            logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 创建用户（用户名{username},邮箱:{email}）成功！')
            flash("用户创建成功", "success")  # 添加成功提示消息
        else:
            flash("创建用户失败。错误：", "danger")  # 添加失败提示消息
            logger.error(
                f'current_user:{current_user.id, current_user.username, current_user.user_unit} 创建用户（邮箱:{email}）失败！')
    except Exception as e:
        flash("创建用户失败。错误：", "danger")
        logger.error(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 创建用户（邮箱:{email}）失败！')
    return redirect(url_for('user_list'))

@app.route('/backup',methods=['POST'])
@login_required
@user_mgmt_permission.require(http_exception=403)
def backup():
    db_backup.backup_database(db_backup.db_name)
    return redirect(url_for('user_list'))

@app.route('/document')
@login_required
@doc_ops_permission.require(http_exception=403)
def document():
    documents = db_document_manager.get_document_list(current_user.id)
    # documents = db_document_manager.get_all_documents()
    return render_template('document_list.html',documents=documents)

@app.route('/document/view/<int:id>')
@login_required
@doc_ops_permission.require(http_exception=403)
def document_view(id):
    #TODO: 添加一个对404页面的处理，以免访问到不存在的文章ID页面时直接报错
    #TODO: 未来研究一下怎么隐藏前端的ID
    view_document = None
    try:
        view_document = db_document_manager.get_document_by_id(id)
        logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 阅读公文（ID:{id}）')
    except Exception as e:
        flash(f"查看公文（ID:{id}）失败")
        logger.error(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 阅读公文（ID:{id}）失败！')
    return render_template('document_view.html',document=view_document)

@app.route('/document/add')
@login_required
@doc_ops_permission.require(http_exception=403)
def document_add():
    form = forms.DocumentAddForm()
    user_name = current_user.username
    form.author_name.data = user_name  # 设置作者名
    form.drafting_unit.data = current_user.user_unit
    form.status.data = "草稿"
    form.modified_date.data = "未提交"
    return render_template('document_add.html', form=form)

@app.route('/document/add_commit', methods=['POST'])
@login_required
@doc_ops_permission.require(http_exception=403)
def document_add_commit():
    form = forms.DocumentAddForm()
    if form.validate_on_submit():
        title = form.title.data
        content = form.content.data
        author_name = form.author_name.data
        drafting_unit = form.drafting_unit.data
        receiving_unit = form.receiving_unit.data
        security_level = form.security_level.data
        action = form.action.data
        user_id = current_user.id
        status_value = "草稿" if action == 'save_draft' else "待审核"

        try:
            db_document_manager.insert_document(
                title, content, user_id, author_name, drafting_unit, receiving_unit, status_value, security_level
            )
            flash('公文{}成功。'.format('草稿已保存' if action == 'save_draft' else '已提交审核'))
            logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 起草公文成功！')
            return redirect(url_for("document"))
        except Exception as e:
            flash("提交失败！ ", str(e))
            logger.error(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 起草公文失败！')
            return redirect(url_for("document"))
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash("{}: {}".format(field, error))
        return redirect(url_for("document_add"))

@app.route('/document/edit/<int:id>/')
@login_required
@doc_ops_permission.require(http_exception=403)
def document_edit(id):
    try:
        result = db_document_manager.get_document_by_id(id)
        form = forms.DocumentEditForm()
        if result:
            form.document_id.data = id  # 将文档ID赋值给表单中的对应字段
            form.title.data = result['title']
            form.content.data = result['content']
            form.author_name.data = result['author_name']
            form.drafting_unit.data = result['drafting_unit']
            form.receiving_unit.data = result['receiving_unit']
            form.status.data = result['status']
            form.security_level.data = result['security_level']
            form.modified_date.data = result['modified_date']
            flash(f"查看可编辑公文（ID:{id}）成功！")
            logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看可编辑公文（ID:{id}）成功！')
        return render_template("document_edit.html", form=form, success='')
    except Exception as e:
        flash(f"查看可编辑公文（ID:{id}）失败！")
        logger.error(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看可编辑公文（ID:{id}）失败！')
        return render_template("document_edit.html", form=None, success='Can\'t edit User: ' + str(e))

@app.route('/document/edit_commit', methods=['POST'])
@login_required
@doc_ops_permission.require(http_exception=403)
def document_edit_commit():
    form  = forms.DocumentEditForm()
    if form.validate_on_submit():
        document_id = form.document_id.data  # 从表单对象中获取文档ID
        title = form.title.data
        content = form.content.data
        security_level = form.security_level.data
        receiving_unit = form.receiving_unit.data
        action = form.action.data
        is_draft = action == 'save_draft'
        if is_draft:
            status = db_document_manager.DocumentStatus.DRAFT
        else:
            status = db_document_manager.DocumentStatus.PENDING_REVIEW
        # 数据校验
        if not document_id or not title or not content:
            raise ValueError("Missing required fields")
        # TODO: 检查是否有不应该的修改，比如密级（权限）、作者、时间等
        try:
            # 更新数据库中的文档
            db_document_manager.update_document_status(document_id, status.value)
            db_document_manager.update_document_edit_commit(document_id, title, content,receiving_unit,security_level)
            flash("公文编辑提交成功！")
            logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 编辑公文（ID:{id}）成功！')
        except Exception as e:
            flash("公文编辑提交失败")
            logger.error(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 编辑公文（ID:{id}）失败！')

        # 重定向到文档页面
        return redirect(url_for("document"))
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash("{}: {}".format(field, error))
        return redirect(url_for("document_edit"))

@app.route('/document/audit/list')
@login_required
@doc_review_permission.require(http_exception=403)
def document_audit_list():
    try:
        criteria = db_document_manager.DocumentQueryCriteria()
        criteria.add_status(db_document_manager.Operator.IN, [db_document_manager.DocumentStatus.PENDING_REVIEW])
        documents = db_document_manager.get_documents_by_criteria(criteria.get_criteria())
        logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看审批公文列表')
    except Exception as e:
        flash("查看审批公文列表失败")
        logger.error(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看审批公文列表失败')
        return redirect(url_for('index'))
    return render_template('document_audit_list.html',documents=documents)

@app.route('/document/audit/<int:id>')
@login_required
@doc_review_permission.require(http_exception=403)
def document_audit(id):
    try:
        result = db_document_manager.get_document_by_id(id)
        form = forms.DocumentAuditForm()
        if result:
            form.document_id.data = result['id']
            form.title.data = result['title']
            form.content.data = result['content']
            form.author_name.data = result['author_name']
            form.status.data = result['status']
            form.receiving_unit.data = result['receiving_unit']
            form.drafting_unit.data = result['drafting_unit']
            form.security_level.data = result['security_level']
            form.modified_date.data = result['modified_date']
            logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看审批公文(ID:{id})')
        return render_template('document_audit.html', form=form)
    except Exception as e:
        logger.error(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看审批公文(ID:{id})失败')
        return render_template('document_audit.html', form=None, error=str(e))

@app.route('/document/audit_commit', methods=['POST'])
@login_required
@doc_review_permission.require(http_exception=403)
def document_audit_commit():
    form = forms.DocumentAuditForm()
    document = {}
    if form.validate_on_submit():
        # 将表单中的各个字段数据存入字典
        document['document_id'] = form.document_id.data
        document['title'] = form.title.data
        document['content'] = form.content.data
        document['author_name'] = form.author_name.data
        document['drafting_unit'] = form.drafting_unit.data
        document['receiving_unit'] = form.receiving_unit.data
        document['status'] = form.status.data
        document['security_level'] = form.security_level.data
        document['modified_date'] = form.modified_date.data
        audit_result = form.audit_result.data
        try:
            # 根据审核结果更新数据库中文档的状态等信息
            if audit_result == '通过':
                new_status = db_document_manager.DocumentStatus.PENDING_SIGN
                document['status'] = db_public_document_manager.PublicDocumentStatus.WAITING_FOR_SIGNATURE
                # 在这里将公文发送给公共数据库
                # 注意，发送单位和接受单位一定要存在才行
                db_public_document_manager.insert_public_document(document)
            else:
                new_status = db_document_manager.DocumentStatus.REVIEW_FAILED
            db_document_manager.update_document_status(document['document_id'], new_status.value)
            logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 提交审批公文结果：{audit_result}')
            flash("审核提交成功！")
            return redirect(url_for('document_audit_list'))
        except Exception as e:
            flash("审核提交失败！")
            logger.error(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 提交审批公文结果({audit_result})失败！')
            return redirect(url_for('document_audit_list'))
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash("{}: {}".format(field, error))
        return redirect(url_for('document_audit_list'))

@app.route('/document/receipt/list')
@login_required
@doc_sign_permission.require(http_exception=403)
def document_receipt_list():
    user_unit = current_user.user_unit
    try:
        public_documents = db_public_document_manager.get_public_document_list(user_unit)
        logger.info(
            f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看签收公文列表)')
    except Exception as e:
        flash("查看签收公文列表失败！")
        logger.error(
            f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看签收公文列表失败)')
        return redirect(url_for('index'))
    return render_template('document_receipt_list.html',documents=public_documents )

@app.route('/document/receipt/view/<int:id>')
@login_required
@doc_sign_permission.require(http_exception=403)
def document_receipt_view(id):
    try:
        public_document = db_public_document_manager.get_public_document_by_id(id)
        de_document = None
        if public_document:
            de_document = public_document['encrypted_data']
            # 如果这个公共公文是待签收，那么将状态更新为已查看
            if public_document['status'] == '待签收':
                db_public_document_manager.update_public_document_view_status(id)
        logger.info(
            f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看签收公文（ID:{id}）)')
    except Exception as e:
        flash("查看公文失败！")
        logger.error(
            f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看签收公文（ID:{id}）失败)')
        return redirect(url_for('document_receipt_list'))
    return render_template('document_receipt_view.html',document=de_document,id=public_document['id'],
                           is_verified=public_document['is_verified'],status=public_document['status'])


@app.route('/document/receipt/commit', methods=['POST'])
@login_required
@doc_sign_permission.require(http_exception=403)
def document_receipt_commit():
    document_id = request.form.get('document_id')  # 获取前端传递过来的公文ID
    sign_status = request.form.get('sign_status')  # 获取前端传递过来的是否签收的选择
    try:
        if not document_id:
            return {'message': '未获取到公文ID，请检查参数传递是否正确', 'success': False}, 400
        if not sign_status:
            return {'message': '未获取到是否签收的选择，请检查参数传递是否正确', 'success': False}, 400
        if sign_status == '签收':
            print("document_id:", document_id)
            db_public_document_manager.update_public_document_sign_status(document_id,True)
            #TODO: 因为公共公文中保存的公文格式中没有附带作者ID，所以无法完成公文归档
            # 并且也没有一个其他的数据库作为真正的单位B来操作。

            # try:
            #     # 将签收成功的公文收录到本地的公文库中
            #     public_document = db_public_document_manager.get_public_document_by_id(document_id)
            #     new_document = public_document['encrypted_data']
            #     new_document['status'] = db_document_manager.DocumentStatus.SIGNED.value
            #     print("new_document: ", new_document)
            #     #
            #     db_document_manager.insert_document(new_document['title'], new_document['content'], new_document['author_id'],
            # new_document['author_name'],new_document['drafting_unit'],new_document['receiving_unit'],new_document['status'],new_document['security_level'])
            #     flash("签收公文归档成功！")
            # except Exception as e:
            #     flash("将签收公文归档失败！")
            #     print(str(e))
            #     logger.error(
            #         f'current_user:{current_user.id, current_user.username, current_user.user_unit} 签收公文归档失败（ID:{document_id}）结果失败)')
        else:
            db_public_document_manager.update_public_document_sign_status(document_id,False)
        logger.info(
            f'current_user:{current_user.id, current_user.username, current_user.user_unit} 提交签收公文（ID:{document_id}）结果{sign_status})')
    except Exception as e:
        flash("提交签收结果失败！")
        logger.error(
            f'current_user:{current_user.id, current_user.username, current_user.user_unit} 提交签收公文（ID:{document_id}）结果失败)')
    return redirect(url_for('document_receipt_list'))


@app.route('/permission_manage')
@login_required
@perm_mgmt_permission.require(http_exception=403)
def permission_manage():
    all_users_data = db_permission_manager.get_all_users_roles_permissions()
    return render_template('permission_manage.html', users_data=all_users_data)

LiteralString = Literal["INFO", "WARNING", "ERROR"]
@app.route('/log_view')
@login_required
@log_mgmt_permission.require(http_exception=403)
def log_view():
    log_data = []
    try:
        with open('logs/app.log', 'r',encoding='utf-8') as file:
            for line in file.readlines():
                parts = line.strip().split('|')
                level = parts[1].strip()
                if level not in ["INFO", "WARNING", "ERROR"]:
                    raise ValueError(f"Invalid log level: {level}")  # 如果不符合预期字面量类型，抛出异常
                log_entry = {
                    'time': parts[0].strip(),
                    'level': level,
                    'message': parts[2].strip()
                }
                log_data.append(log_entry)
        logger.info(f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看日志文件)')
    except FileNotFoundError:
        flash("日志文件不存在")
        logger.error(
            f'current_user:{current_user.id, current_user.username, current_user.user_unit} 查看日志文件失败)')
        return redirect(url_for('index'))
    return render_template('log_view.html', log_data=log_data)

@app.route('/about')
def about():
    return render_template('about.html')

@app.errorhandler(403)
def permission_denied(error):
    logger.warning(
        f'current_user:{current_user.id, current_user.username, current_user.user_unit} 越权访问记录。具体错误：{str(error)})')
    return render_template('403.html'), 403

if __name__ == '__main__':
    import db_key_manager
    db_key_manager.apply_test_data()
    # import skf_sm4_file
    # try:
    #     skf_sm4_file.skf_init_sm4_file(skf_sm4_file.sm4_key)
    # except:  # 这样会捕获所有类型的异常，通常不推荐，建议明确指定具体异常类型来捕获
    #     print("硬件初始化失败。可能是未插入或默认PIN错误或其他因素")
    # 启动Flask应用，开启调试模式
    # app.run(debug=True)
    app.run(debug=True,ssl_context=('cert.pem', 'key.pem'))

